package org.msb.accentis.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import org.apache.openjpa.persistence.OpenJPAEntityManager;

public class DataManager implements IDataManager {

	private final EntityManagerFactory emf;

	private ThreadLocal<EntityManager> entityManagers = new ThreadLocal<EntityManager>() {
		@Override
		protected EntityManager initialValue() {
			return emf.createEntityManager();
		}
	};

	@SuppressWarnings("rawtypes")
	private Map dataManagerListeners = new HashMap();

	public DataManager(EntityManagerFactory factory) {
		emf = factory;
	}

	@Override
	public synchronized void initialize() {
		entityManagers.get();
	}

	@Override
	public <T> void refresh(T object) {
		EntityManager em = entityManagers.get();
		EntityTransaction tx = em.getTransaction();
		try {
			if (!((OpenJPAEntityManager) em).isPersistent(object))
				return;

			tx.begin();

			em.refresh(object);

			tx.commit();

			fireObjectRefreshed(object);
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();
			tx.rollback();
		}
	}

	@Override
	public <T> List<T> runNamedQuery(String namedQuery) {
		return runNamedQuery(namedQuery, QueryParameterMap.NO_PARAMETERS);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> List<T> runNamedQuery(String namedQuery, Map<String, Object> parameters) {
		EntityManager em = entityManagers.get();
		List<T> list = new ArrayList<T>();
		EntityTransaction tx = em.getTransaction();
		try {
			tx.begin();

			Query query = em.createNamedQuery(namedQuery);
			for (String parameter : parameters.keySet())
				query.setParameter(parameter, parameters.get(parameter));

			list.addAll(query.getResultList());

			tx.commit();
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();
			tx.rollback();
		}

		return list;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T runSingleResultNamedQuery(String namedQuery, Map<String, Object> parameters) {
		EntityManager em = entityManagers.get();
		T object = null;
		EntityTransaction tx = em.getTransaction();

		try {
			tx.begin();

			Query query = em.createNamedQuery(namedQuery);
			for (String parameter : parameters.keySet())
				query.setParameter(parameter, parameters.get(parameter));

			object = (T) query.getSingleResult();

			tx.commit();
		} catch (NoResultException ex) {
			tx.commit();
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();

			if (tx.isActive())
				tx.rollback();
		}

		return object;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T runSingleResultNamedQuery(String namedQuery) {
		EntityManager em = entityManagers.get();
		T object = null;
		EntityTransaction tx = em.getTransaction();

		try {
			tx.begin();

			Query query = em.createNamedQuery(namedQuery);

			object = (T) query.getSingleResult();

			tx.commit();
		} catch (NoResultException ex) {
			tx.commit();
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();

			if (tx.isActive())
				tx.rollback();
		}

		return object;
	}

	@Override
	public <T> T persist(T object) {
		EntityManager em = entityManagers.get();
		EntityTransaction tx = em.getTransaction();

		try {
			tx.begin();

			em.persist(object);

			tx.commit();

			fireObjectCreated(object);
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();

			if (tx.isActive())
				tx.rollback();
		}

		return object;
	}

	@Override
	public <T> void remove(T object) {
		EntityManager em = entityManagers.get();
		EntityTransaction tx = em.getTransaction();

		try {
			tx.begin();

			em.remove(object);

			tx.commit();

			fireObjectRemoved(object);
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();
			tx.rollback();
		}
	}

	@Override
	public <T> void save(T object) {
		EntityManager em = entityManagers.get();
		EntityTransaction tx = em.getTransaction();

		try {
			tx.begin();

			em.merge(object);

			tx.commit();

			fireObjectSaved(object);
		} catch (Throwable th) {
			System.err.println(th.getMessage());
			th.printStackTrace();
			tx.rollback();
		}
	}

	@Override
	public void closeEntityManager() {
		emf.close();
	}

	@Override
	public void flushData() {
		entityManagers.get().flush();
	}

	@Override
	public <T> void addDataManagerListener(IDataManagerListener<T> listener, Class<T> objectClass) {
		Set<IDataManagerListener<T>> classListeners = getDataManagerListenersForClass(objectClass);
		classListeners.add(listener);
	}

	@SuppressWarnings("unchecked")
	private <T> Set<IDataManagerListener<T>> getDataManagerListenersForClass(Class<T> objectClass) {
		Set<IDataManagerListener<T>> classListeners = (Set<IDataManagerListener<T>>) dataManagerListeners.get(objectClass);
		if (null == classListeners) {
			classListeners = new HashSet<IDataManagerListener<T>>();
			dataManagerListeners.put(objectClass, classListeners);
		}

		return classListeners;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> void removeDataManagerListener(IDataManagerListener<T> listener) {
		for (Set<IDataManagerListener<T>> dataManagerListenerSet : ((Map<Class<T>, Set<IDataManagerListener<T>>>) dataManagerListeners).values())
			dataManagerListenerSet.remove(listener);
	}

	@SuppressWarnings("unchecked")
	private <T> void fireObjectCreated(T object) {
		Class<T> objectClass = (Class<T>) object.getClass();
		Set<IDataManagerListener<T>> classListeners = getDataManagerListenersForClass(objectClass);
		for (IDataManagerListener<T> listener : classListeners)
			listener.objectCreated(object);
	}

	@SuppressWarnings("unchecked")
	private <T> void fireObjectRemoved(T object) {
		Class<T> objectClass = (Class<T>) object.getClass();
		Set<IDataManagerListener<T>> classListeners = getDataManagerListenersForClass(objectClass);
		for (IDataManagerListener<T> listener : classListeners)
			listener.objectRemoved(object);
	}

	@SuppressWarnings("unchecked")
	private <T> void fireObjectRefreshed(T object) {
		Class<T> objectClass = (Class<T>) object.getClass();
		Set<IDataManagerListener<T>> classListeners = getDataManagerListenersForClass(objectClass);
		for (IDataManagerListener<T> listener : classListeners)
			listener.objectRefreshed(object);
	}

	@SuppressWarnings("unchecked")
	private <T> void fireObjectSaved(T object) {
		Class<T> objectClass = (Class<T>) object.getClass();
		Set<IDataManagerListener<T>> classListeners = getDataManagerListenersForClass(objectClass);
		for (IDataManagerListener<T> listener : classListeners)
			listener.objectSaved(object);
	}
}
